/** copyright (c) 2020 by XMX
 *	file : tyger.c
 *	since : 2020-08-22
 */

/* $begin tyger.c */

#include"tyger.h"

/* Error-handling functions */
void unix_error (char *msg)
{
	perror(msg);
	exit(EXIT_FAILURE);
}

/* Process control wrappers */
pid_t Fork (void)
{
	pid_t pid;

	if ((pid = fork()) < 0)
		unix_error("fork");
	return pid;
}

void Execve (const char *filename, char *const argv[], char *const envp[])
{
	if (execve(filename, argv, envp) < 0)
		unix_error("execve");
}

pid_t Wait (int *status)
{
	pid_t rc;

	if ((rc = wait(status)) < 0)
		unix_error("wait");
	return rc;
}

pid_t Waitpid (pid_t pid, int *status, int options)
{
	pid_t rspid;
	
	if ((rspid = waitpid(pid, status, options)) < 0)
		unix_error("waitpid");
	return rspid;
}

/* Signal wrappers */
handler_t *Signal (int sig, handler_t *handler)
{
	struct sigaction action, old_action;
	
	action.sa_handler = handler;
	sigemptyset(&action.sa_mask);	/* initializes the signal set to empty */
	action.sa_flags = SA_RESTART;	/* automatically restarts the syscall interupted by handler */
	
	if (sigaction(sig, &action, &old_action) < 0)
		unix_error("sigaction");
	return (old_action.sa_handler);
}

/* Signalhandler for SIGCHLD */
void SigHandler (int sig)
{
	if (sig == SIGCHLD) {
		while (waitpid(-1, NULL, WNOHANG) > 0) 
			printf("sig_handler call\n");
	}
}

/* Unix I/O wrappers */
int Open (const char *pathname, int flags, mode_t mode)
{
	int fd;
	
	if ((fd = open(pathname, flags, mode)) < 0)
		unix_error("open");
	return fd;
}

ssize_t Read (int fd, void *buf, size_t count)
{
	ssize_t rc;
	
	if ((rc = read(fd, buf, count)) < 0)
		unix_error("read");
	return rc;
}

ssize_t Write (int fd, const void *buf, size_t count)
{
	ssize_t rc;
	
	if ((rc = write(fd, buf, count)) < 0)
		unix_error("write");
	return rc;
}

void Close (int fd)
{
	int rc;
	
	if ((rc = close(fd)) < 0)
		unix_error("close");
}

int Dup2 (int fd1, int fd2)
{
	int rc;
	
	if ((rc = dup2(fd1, fd2)) < 0)
		unix_error("dup2");
	return rc;
}

void Stat (const char *pathname, struct stat *statbuf)
{
	if (stat(pathname, statbuf) < 0)
		unix_error("stat");
}

void Fstat (int fd, struct stat *statbuf)
{
	if (fstat(fd, statbuf) < 0)
		unix_error("fstat");
}

/* Memory mapping wrappers */
void *Mmap (void *addr, size_t length, int prot, int flags, int fd, off_t offset)
{
	void *ptr;

	if ((ptr = mmap(addr, length, prot, flags, fd, offset)) == ((void *)-1))
		unix_error("mmap");
	return ptr;
}

void Munmap (void *addr, size_t length)
{
	if (munmap(addr, length) < 0)
		unix_error("munmap");
}

/* Socket interface wrappers */
int Socket (int domain, int type, int protocol)
{
	int fd;
	
	if ((fd = socket(domain, type, protocol)) < 0)
		unix_error("socket");
	return fd;
}

void Setsockopt (int sockfd, int level, int optname, const void *optval, socklen_t optlen)
{
	if (setsockopt(sockfd, level, optname, optval, optlen) < 0)
		unix_error("setsockopt");
}

void Bind (int sockfd, struct sockaddr *addr, socklen_t addrlen)
{
	if (bind(sockfd, addr, addrlen) < 0)
		unix_error("bind");
}

void Listen (int sockfd, int backlog)
{
	if (listen(sockfd, backlog) < 0)
		unix_error("listen");
}

int Accept (int listenfd, struct sockaddr *addr, socklen_t *addrlen)
{
	int fd;
	
	if ((fd = accept(listenfd, addr, addrlen)) < 0)
		unix_error("accept");
	return fd;
}

void Connect (int sockfd, struct sockaddr *addr, socklen_t addrlen)
{
	if (connect(sockfd, addr, addrlen) < 0)
		unix_error("connect");	
}

/* Protocol independent wrappers */
void Getaddrinfo (const char *host, const char *service,
const struct addrinfo *hints, struct addrinfo **result)
{
	if (getaddrinfo(host, service, hints, result) != 0)
		unix_error("getaddrinfo");
}

void Getnameinfo (const struct sockaddr *addr, socklen_t addrlen, char *host, size_t hostlen, char *service, size_t servlen, int flags)
{
	if (getnameinfo(addr, addrlen, host, hostlen, service, servlen, flags) != 0)
		unix_error("getnameinfo");
}

void Freeaddrinfo (struct addrinfo *result)
{
	freeaddrinfo(result);
}

/* Rio package */

/* $begin Robust I/O (unbuffered) */
ssize_t rio_readn (int fd, void *usrbuf, size_t n)
{
	size_t nleft = n;
	ssize_t nread;
	char *bufp = usrbuf;

	while (nleft > 0) {
		if ((nread = read(fd, bufp, n)) < 0) {
			if (nread == EINTR)
				nread = 0;
			else 
				return -1;
		} else if (nread == 0)
			break;
		nleft -= nread;
		bufp += nread;
	}
	return (n - nleft);
}

ssize_t rio_writen(int fd, void *usrbuf, size_t n)
{
	size_t nleft = n;
	ssize_t nwrite;
	char *bufp = usrbuf;

	while (nleft > 0) {
		if ((nwrite = write(fd, bufp, n)) < 0) {
			if (nwrite == EINTR)
				nwrite = 0;
			else 
				return -1;
		}
		nleft -= nwrite;
		bufp += nwrite;
	}
	return n;
}
/* $end Unbuffered RIO */

/* $begin Buffered RIO */
void rio_readinitb (rio_t *rp, int fd)
{
	rp->rio_fd = fd;
	rp->rio_cnt = 0;
	rp->rio_bufptr = rp->rio_buf;
}

/* $end Buffered RIO */
/* base function */
static ssize_t rio_read (rio_t *rp, char *usrbuf, size_t n)
{
	int cnt;
	
	while (rp->rio_cnt <= 0) {
		rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, sizeof(rp->rio_buf));
		if (rp->rio_cnt < 0 && rp->rio_cnt != EINTR)
			return -1;
		else if (rp->rio_cnt == 0)
			return 0;
		else 
			rp->rio_bufptr = rp->rio_buf;
	}

	/* Copy min(rp->cnt, n) bytes from internal buf to user buf */
	cnt = n;
	if (rp->rio_cnt < n)
		cnt = rp->rio_cnt;
	memcpy(usrbuf, rp->rio_bufptr, cnt);
	rp->rio_bufptr += cnt;
	rp->rio_cnt -= cnt;
	return cnt;
	
}

ssize_t rio_readnb (rio_t *rp, void *usrbuf, size_t n)
{
	size_t nleft = n;
	ssize_t nread;
	char *bufp = usrbuf;
	
	while (nleft > 0) {
		if ((nread = rio_read(rp, usrbuf, n)) < 0)
			return -1;
		else if (nread == 0)
			break;
		nleft -= nread;
		bufp += nread;
	}
	return (n - nleft);
}

ssize_t rio_readlineb (rio_t *rp, void *usrbuf, size_t maxlen)
{
	int n, rc;
	char c, *bufp = usrbuf;

	for (n = 1; n < maxlen; ++n) {
		if ((rc = rio_read(rp, &c, 1)) == 1) {
			*bufp++ = c;
			if (c == '\n') {
				++n;
				break;
			}
		} else if (rc == 0) {
			if (n == 1)
				return 0;
			else
				break;
		} else {
			return -1;
		}
	}
	*bufp = 0;
	return (n - 1);
}

ssize_t Rio_readn (int fd, void *usrbuf, size_t n)
{
	ssize_t rc;
	
	if ((rc = rio_readn(fd, usrbuf, n)) < 0)
		unix_error("rio_readn");
	return rc;
}

void Rio_writen (int fd, void *usrbuf, size_t n)
{
	if (rio_writen(fd, usrbuf, n) != n)
		unix_error("rio_writen");
}

void Rio_readinitb(rio_t *rp, int fd)
{
	rio_readinitb(rp, fd);
}

ssize_t Rio_readnb (rio_t *rp, void *usrbuf, size_t n)
{
	ssize_t rc;
	
	if ((rc = rio_readnb(rp, usrbuf, n)) < 0)
		unix_error("rio_readnb");
	return rc;
}

ssize_t Rio_readlineb (rio_t *rp, void *usrbuf, size_t maxlen)
{
	ssize_t rc;
	
	if ((rc = rio_readlineb(rp, usrbuf, maxlen)) < 0)
		unix_error("rio_readlineb");
	return rc;
}

/*
 *	open_listenfd - Open and return a listening socket on port.
 *	This funciton is reentrant and protocol-independent.
 *	
 *	On error, returns:
 *		-2 for getaddrinfo error
 * 		-1 with errno set for other errors.
 	*/
int open_listenfd (char *port)
{
	struct addrinfo hints, *listp, *p;
	int listenfd, rc, optval = 1;

	/* Get a list of potential server address */
	memset(&hints, 0, sizeof(struct addrinfo));
	//hints.ai_family = AF_INET;	//specify to get a IPv4
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;  //AI_PASSIVE -> return a 
	hints.ai_flags |= AI_NUMERICSERV;			  //listen,generic address
	if ((rc = getaddrinfo(NULL, port, &hints, &listp)) != 0) {
		fprintf(stderr, "getaddrinfo failed (port %s): %s\n",
				 port, gai_strerror(rc));
		return -2;
	}

	/* Walk the list for one that we can bind to */
	for (p = listp; p; p = p->ai_next) {
		
#if 1	//	debug code
		char hst[200], serv[200];
		size_t hlen = 200, slen = 200;
		
		if (getnameinfo(p->ai_addr, p->ai_addrlen, hst, hlen, serv, slen, 0) != 0)
			unix_error("getnameinfo in open_listenfd");
		printf("address (%s, %s)\n", hst, serv);
#endif

		if ((listenfd = socket(p->ai_family, 
			p->ai_socktype, p->ai_protocol)) < 0)
			continue;
		/* notice: should call the setsockopt() before call the bind() */
		setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
				 (const void *)&optval, sizeof(int));
		if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
			break;	/* Success */
		if (close(listenfd) < 0) {
			fprintf(stderr, "close failed: %s\n", strerror(errno));
			return -1;
		}
	}

	/* Clean up */
	freeaddrinfo(listp);
	if (!p)
		return -1;
	
	if (listen(listenfd, LISTENQ) < 0) {
		close(listenfd);
		return -1;
	}
	return listenfd;
}

/* Wrappers for reentrant protocol -independent heplers */
int Open_listenfd (char *port)
{
	int rc;

	if ((rc = open_listenfd(port)) < 0)
		unix_error("open_listenfd");
	return rc;
}



/* $end tyger.c */
